package com.ficus.road.maintain.process;

import com.ficus.road.maintain.config.DiseaseConfig;
import com.ficus.road.maintain.config.DiseaseConfigInfo;
import com.ficus.road.maintain.core.model.*;
import com.ficus.road.maintain.dto.FrameResultDTO;
import com.ficus.road.maintain.grpc.RequestConverter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import ficus.car_demo.FrameMeta;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;

import java.util.*;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

@Slf4j
public abstract class BaseProcessor implements Runnable {

    public final int DISTANCE_DEFAULT;
    public BlockingDeque<FrameResultDTO> blockingDeque;
    public boolean hitCheck;
    public AtomicBoolean run;
    /**
     * <carId, <diseaseType, hitProperties>>
     */
    Map<String, Map<String, HitProperties>> lastHitMap;
    DiseaseConfig diseaseConfig;


    public BaseProcessor(DiseaseConfig diseaseConfig, int maxDistance, boolean hitCheck) {
        this.diseaseConfig = diseaseConfig;
        this.blockingDeque = new LinkedBlockingDeque<>(getMaxQueueSize());
        this.DISTANCE_DEFAULT = maxDistance;
        this.hitCheck = hitCheck;
        run = new AtomicBoolean(true);
        lastHitMap = Maps.newConcurrentMap();
    }

    /**
     * 两个子类的run方法相互不影响, 各自拥有自己的blockingDeque
     */
    @Override
    public void run() {
        while (run.get()) {
            try {
                //把uuid封装了一下
                FrameResultDTO frameResultDTO = blockingDeque.take();
//                log.info("take frame from deque, uuid={}", frameResultDTO.getUuid());
                onProcess(frameResultDTO);
            } catch (Exception e) {
                log.error("Base message processor error", e);
            }
        }
    }

    public abstract void saveHitInfo(FrameDTO value, List<Hit> hits);

    public abstract void sendDrawer(FrameDTO value);

    public abstract boolean isWsConnect(String uuid);

    public void onProcess(FrameResultDTO frameResultDTO) {

        FrameDTO value = RequestConverter.request2FrameDTO(frameResultDTO.getFrameResult(), diseaseConfig);
        String uuid = frameResultDTO.getUuid();
        value.setUuid(uuid);
        String picturePath = frameResultDTO.getPicturePath();
        value.setPicturePath(picturePath);
        String nextPicturePath = frameResultDTO.getNextPicturePath();
        value.setNextPicturePath(nextPicturePath);
        Boolean compressed = frameResultDTO.getCompressed();
        Boolean nextCompressed = frameResultDTO.getNextCompressed();
        value.setCompressed(compressed);
        value.setNextCompressed(nextCompressed);
        List<Hit> generateHits = generateHit(value);
        //去空
        List<Hit> hits = generateHits.stream().filter(ObjectUtils::isNotEmpty).collect(Collectors.toList());
        //将病害存入数据库(DataProcessor重写了该方法，ReplayDataProcessor重写了，但是是空方法)
        saveHitInfo(value, hits);
        //没有ws连接则不做解码，不播放视频
        if (isWsConnect(uuid)) {
            sendDrawer(value);
        }
    }

    /**
     * 强制插入, 如果满了，就从队列删除元素再插入
     *
     * @param request
     * @throws InterruptedException
     */
    public void forcePut(FrameMeta.FrameResult request, String picturePath, String nextPicturePath, Boolean compressed, Boolean nextCompressed) throws InterruptedException {
        if (null == request) {
            return;
        }
        if (blockingDeque.size() >= getMaxQueueSize()) {
            log.info("queue is full,poll one");
            FrameResultDTO poll = blockingDeque.poll();
            log.info("delete frame: {}", poll.getFrameResult().getSequenceId());
        }
        blockingDeque.put(new FrameResultDTO(null, picturePath, nextPicturePath, compressed, nextCompressed, request));
    }

    /**
     * 普通插入，知道有空间再插入
     *
     * @param frameResultDTO
     * @throws InterruptedException
     */
    public void put(FrameResultDTO frameResultDTO) throws InterruptedException {
        if (null == frameResultDTO) {
            return;
        }
        log.info("put frame into deque! uuid = {}", frameResultDTO.getUuid());
        blockingDeque.put(frameResultDTO);
    }

    public abstract int getMaxQueueSize();

    public abstract Confirmation.ConfirmationStatus getHitStatus();

    public List<Hit> generateHit(FrameDTO value) {
        String carId = value.getCarId();
        //设计的时候是设计成一帧里可能有多个道路病害，但实际上就一个病害
        List<RoadDiseaseResult> roadDiseaseResult = value.getRoadDiseaseResult();
        List<Hit> hits = new ArrayList<>();

        //hitCheck为false，那么就根据该帧去创建病害（一般情况连续若干帧里只有一个为false（取一个分数最高的帧来创建病害）；造数据的时候，连续若干帧都为false，存为hit）
//        if (!hitCheck) {
//            Map<String, List<RoadDiseaseResult>> collect = roadDiseaseResult.stream().collect(Collectors.groupingBy(RoadDiseaseResult::getType));
//            collect.forEach((type, diseaseResult) -> {
//                Hit hit = createHit(value, diseaseResult, type);
//                hits.add(hit);
//            });
//            return hits;
//        }

        Map<String, HitProperties> hitPropertiesMap = lastHitMap.getOrDefault(carId, new HashMap<>());
        GpsLocation currentGpsLocation = GpsLocation.builder()
                .longitude(value.getGpsLocation().getLongitude())
                .latitude(value.getGpsLocation().getLatitude())
                .build();
        if (CollectionUtils.isNotEmpty(roadDiseaseResult) && !value.getGpsLocation().getLongitude().equals(0d) && !value.getGpsLocation().getLatitude().equals(0d)) {
            Map<String, List<RoadDiseaseResult>> collect = roadDiseaseResult.stream().collect(Collectors.groupingBy(RoadDiseaseResult::getType));
            collect.forEach((type, diseaseResult) -> {
                HitProperties properties = hitPropertiesMap.get(type);
                Hit hit = createHit(value, diseaseResult, type);
                if (Objects.isNull(properties)) {
                    if (ObjectUtils.isNotEmpty(hit)) {
                        HitProperties build = HitProperties.builder()
                                .possibleHit(hit)
                                .firstHitGpsLocation(currentGpsLocation)
                                .build();
                        hitPropertiesMap.put(type, build);
                    }
                    hits.add(hit);
                } else {
                    double distance = GpsLocation.calDistance(properties.getFirstHitGpsLocation(), currentGpsLocation);
                    if (distance < DISTANCE_DEFAULT) {

                    } else {
                        if (ObjectUtils.isNotEmpty(hit)) {
                            hits.add(hit);
                            properties.setFirstHitGpsLocation(currentGpsLocation);
                            properties.setPossibleHit(hit);
                            hitPropertiesMap.put(type, properties);
                        }
                    }
                }
            });
        }
        lastHitMap.put(carId, hitPropertiesMap);
        return hits;

//        if (CollectionUtils.isEmpty(roadDiseaseResult)) {
//            if (MapUtils.isEmpty(hitPropertiesMap)) {
//                return new ArrayList<>();
//            }
//            Iterator<Map.Entry<String, HitProperties>> it = hitPropertiesMap.entrySet().iterator();
//            while (it.hasNext()) {
//                Map.Entry<String, HitProperties> next = it.next();
//                HitProperties hitProperties = next.getValue();
//                if (GpsLocation.calDistance(hitProperties.firstHitGpsLocation, currentGpsLocation) > DISTANCE_DEFAULT) {
//                    if (ObjectUtils.isNotEmpty(hitProperties.getPossibleHit())) {
//                        hits.add(hitProperties.getPossibleHit());
//                        it.remove();
//                    }
//                }
//            }
//            return hits;
//        }
//
//        if (MapUtils.isEmpty(hitPropertiesMap)) {
//            hitPropertiesMap = Maps.newHashMap();
//            lastHitMap.put(carId, hitPropertiesMap);
//        }

        //分析报警
//        Map<String, List<RoadDiseaseResult>> collect = roadDiseaseResult.stream().collect(Collectors.groupingBy(RoadDiseaseResult::getType));
//        Map<String, HitProperties> finalHitPropertiesMap = hitPropertiesMap;
//        collect.forEach((type, diseaseResult) -> {
//            HitProperties properties = finalHitPropertiesMap.get(type);
//            if (Objects.isNull(properties)) {
//                Hit hit = createHit(value, diseaseResult, type);
//                if (ObjectUtils.isNotEmpty(hit)) {
//                    HitProperties build = HitProperties.builder()
//                            .possibleHit(hit)
//                            .build();
//                    finalHitPropertiesMap.put(type, build);
//                }
//                return;
//            }
//            double distance = GpsLocation.calDistance(properties.getFirstHitGpsLocation(), currentGpsLocation);
//            if (distance < DISTANCE_DEFAULT) {
//
//            } else {
//                if (ObjectUtils.isNotEmpty(properties.getPossibleHit())) {
//                    hits.add(properties.getPossibleHit());
//                }
//                properties.setFirstHitGpsLocation(currentGpsLocation);
//                properties.setPossibleHit(createHit(value, diseaseResult, type));
//            }
//        });
//        return hits;
    }

    private Hit createHit(FrameDTO value, List<RoadDiseaseResult> roadDiseaseResults, String type) {
        String finalType = type;
        List<HitSubject> subjects = roadDiseaseResults.stream().map(t -> HitSubject.builder()
                .bbox(t.getBbox())
                .relBbox(t.getRelBbox())
                .severityType(t.getSeverityType())
                .width(t.getWidth())
                .length(t.getLength())
                .area(t.getArea())
                .angle(t.getAngle())
                .heightDiff(t.getHeightDiff())
                .fallDownNumber(t.getFallDownNumber())
                .comfortRate(t.getComfortRate())
                .headCount(t.getHeadCount())
                .roadType(t.getRoadType())
                .needPushOut(t.getNeedPushOut())
                .type(finalType)
                .status(t.getStatus())
                //新增
                .diseaseUid(t.getDiseaseUid())
                .build()).collect(Collectors.toList());
        SeverityType maxType = null;
        String roadType = null;
        //新增
        String diseaseUid = null;
        int width = 0;
        float length = 0;
        float area = 0;
        float angle = 0;
        int heightDiff = 0;
        int fallDownNumber = 0;
        Boolean needPushOut = null;
        Integer headCount = null;
        Float comfortRate = null;
        HitStatusEnum hitStatusEnum = HitStatusEnum.NEW;

        //疑问：这里的循环是在干嘛？
        for (HitSubject subject : subjects) {
            if (maxType == null) {
                maxType = subject.getSeverityType();
                area = subject.getArea();
                length = subject.getLength();
                width = subject.getWidth();
                angle = subject.getAngle();
                heightDiff = subject.getHeightDiff();
                fallDownNumber = subject.getFallDownNumber();
                comfortRate = subject.getComfortRate();
                headCount = subject.getHeadCount();
                roadType = subject.getRoadType();
                needPushOut = subject.getNeedPushOut();
                type = subject.getType();
                hitStatusEnum = subject.getStatus();
                diseaseUid = subject.getDiseaseUid();

            } else {
                if (maxType.name().compareTo(subject.getSeverityType().name()) < 0) {
                    maxType = subject.getSeverityType();
                    area = subject.getArea();
                    length = subject.getLength();
                    width = subject.getWidth();
                    angle = subject.getAngle();
                    heightDiff = subject.getHeightDiff();
                    fallDownNumber = subject.getFallDownNumber();
                    type = subject.getType();
                    comfortRate = subject.getComfortRate();
                    headCount = subject.getHeadCount();
                    roadType = subject.getRoadType();
                    needPushOut = subject.getNeedPushOut();
                    hitStatusEnum = subject.getStatus();
                    //
                    diseaseUid = subject.getDiseaseUid();
                }
            }
        }
        long time = System.currentTimeMillis() / 1000;
        DiseaseConfigInfo diseaseConfigInfo = diseaseConfig.getMap().get(type);
        //只有在配置文件中的类型的病害才产生，其他的去掉
        if (Objects.nonNull(diseaseConfigInfo)) {
            Integer limitHour = diseaseConfigInfo.getLimitHour();
            String scene = diseaseConfigInfo.getScene();
            String classification = diseaseConfigInfo.getClassification();
            Integer loop = diseaseConfigInfo.getLoop();
            HitAttrs attrs = HitAttrs.builder()
                    .carId(value.getCarId())
                    .gpsLocation(GpsLocation.builder()
                            .latitude(value.getGpsLocation().getLatitude())
                            .longitude(value.getGpsLocation().getLongitude())
                            .build())
                    .location(Lists.newArrayList(value.getGpsLocation().getLongitude(), value.getGpsLocation().getLatitude()))
                    .sceneImage(value.getDiseasePicture())
                    .source(SourceEnum.NORMAL.toString())
                    .status(hitStatusEnum)
                    .picturePath(value.getPicturePath())
                    .roadType(roadType)
                    .limitHour(limitHour)
                    .confirmation(Confirmation.builder().status(getHitStatus()).build())
                    .loop(loop)
                    .diseaseUid(diseaseUid)
                    .build();
            //产生报警
            return Hit.builder()
                    .creationTime(time)
                    .firstHitTime(value.getTimeStamp() / 1000)
                    .hitTime(value.getTimeStamp() / 1000)
                    .type(type)
                    .lastModificationTime(time)
                    .subjects(subjects)
                    .attrs(attrs)
                    .width(width)
                    .area(area)
                    .length(length)
                    .angle(angle)
                    .heightDiff(heightDiff)
                    .fallDownNumber(fallDownNumber)
                    .comfortRate(comfortRate)
                    .headCount(headCount)
                    .roadType(roadType)
                    .scene(scene)
                    .classification(classification)
                    .needPushOut(needPushOut)
                    .severityType(maxType)
                    .algoVersion(value.getAlgoVersion())
                    .build();
        } else {
            log.info("final_type:{}", type);
        }
        return null;
    }

    @Data
    @Builder
    @AllArgsConstructor
    public static class HitProperties {
        GpsLocation firstHitGpsLocation;
        Hit possibleHit;
    }

}
